﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Devonline.Core;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace Devonline.Identity
{
    /// <summary>
    /// Creates a new instance of a persistence store for the specified user type.
    /// </summary>
    /// <typeparam name="TUser">The type representing a user.</typeparam>
    public class UserStore : UserStore<User, Role, UserClaim, UserRole, UserLogin, UserToken, RoleClaim, Group, UserGroup, Level, DbContext, string>
    {
        /// <summary>
        /// Constructs a new instance of <see cref="UserStore{TUser}"/>.
        /// </summary>
        /// <param name="context">The <see cref="DbContext"/>.</param>
        /// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
        public UserStore(DbContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }
    }

    /// <summary>
    /// Represents a new instance of a persistence store for the specified user and role types.
    /// </summary>
    /// <typeparam name="TUser">The type representing a user.</typeparam>
    /// <typeparam name="TRole">The type representing a role.</typeparam>
    /// <typeparam name="TContext">The type of the data context class used to access the store.</typeparam>
    /// <typeparam name="TKey">The type of the primary key for a role.</typeparam>
    public class UserStore<TUser, TRole, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim, TGroup, TUserGroup, TLevel, TContext, TKey> :
        UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim>,
        IIdentityStore<TUser, TKey>,
        IUserGroupStore<TUser, TKey>,
        ILevelStore<TUser, TKey>
        where TKey : IEquatable<TKey>, IConvertible
        where TContext : DbContext
        where TRole : Role<TKey>, new()
        where TUser : User<TKey>, new()
        where TGroup : Group<TKey>, new()
        where TUserGroup : UserGroup<TKey>, new()
        where TUserClaim : UserClaim<TKey>, new()
        where TUserRole : UserRole<TKey>, new()
        where TUserLogin : UserLogin<TKey>, new()
        where TUserToken : UserToken<TKey>, new()
        where TRoleClaim : RoleClaim<TKey>, new()
        where TLevel : Level<TKey>, new()
    {
        /// <summary>
        /// Constructs a new instance of <see cref="UserStore{TUser, TRole, TContext, TKey}"/>.
        /// </summary>
        /// <param name="context">The <see cref="DbContext"/>.</param>
        /// <param name="describer">The <see cref="IdentityErrorDescriber"/>.</param>
        public UserStore(TContext context, IdentityErrorDescriber describer = null) : base(context, describer) { }

        protected DbSet<TGroup> Groups => Context.Set<TGroup>();
        protected DbSet<TUserGroup> UserGroups => Context.Set<TUserGroup>();
        protected DbSet<TLevel> Levels => Context.Set<TLevel>();

        #region implement from IIdentityStore
        /// <summary>
        /// 使用 id 获取当前用户
        /// </summary>
        /// <param name="id"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual Task<TUser> FindByIdAsync(TKey id, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            if (id.Equals(default))
            {
                throw new ArgumentNullException(nameof(id));
            }

            return Context.Set<TUser>().FindAsync(new object[] { id }, cancellationToken).AsTask();
        }
        /// <summary>
        /// 修改名字
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="name"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual Task SetName(TUser user, string name, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            user.Name = name;
            return Task.CompletedTask;
        }
        /// <summary>
        /// 修改昵称
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="alias"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual Task SetAlias(TUser user, string alias, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            user.Alias = alias;
            return Task.CompletedTask;
        }
        /// <summary>
        /// 修改头像
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="image"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual Task SetImage(TUser user, string image, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            user.Image = image;
            return Task.CompletedTask;
        }
        #endregion

        #region implement from IUserGroupStore
        /// <summary>
        /// 用户添加到组织(组)
        /// </summary>
        /// <param name="user"></param>
        /// <param name="groupName"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IdentityResult> AddToGroupAsync(TUser user, string groupName, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            if (user == null || user.Id.Equals(default))
            {
                throw new ArgumentNullException(nameof(user));
            }

            if (groupName.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException(nameof(groupName));
            }

            var group = await Groups.FirstOrDefaultAsync(x => x.Name == groupName, cancellationToken);
            if (group == null)
            {
                return IdentityResult.Failed(ErrorDescriber.InvalidGroupName(groupName));
            }

            if (await UserGroups.AnyAsync(x => x.UserId.Equals(user.Id) && x.GroupId.Equals(group.Id), cancellationToken))
            {
                return IdentityResult.Failed(ErrorDescriber.UserAlreadyInGroup(user.Name, groupName));
            }

            try
            {
                await UserGroups.AddAsync(new TUserGroup { UserId = user.Id, GroupId = group.Id }, cancellationToken);
                await SaveChanges(cancellationToken);
            }
            catch (DbUpdateConcurrencyException)
            {
                return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
            }

            return IdentityResult.Success;
        }
        /// <summary>
        /// 从组织中移除用户
        /// </summary>
        /// <param name="user"></param>
        /// <param name="groupName"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IdentityResult> RemoveFromGroupAsync(TUser user, string groupName, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            if (user == null || user.Id.Equals(default))
            {
                throw new ArgumentNullException(nameof(user));
            }

            if (groupName.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException(nameof(groupName));
            }

            var group = await Groups.FirstOrDefaultAsync(x => x.Name == groupName, cancellationToken);
            if (group == null)
            {
                return IdentityResult.Failed(ErrorDescriber.InvalidGroupName(groupName));
            }

            var userGroup = await UserGroups.FirstOrDefaultAsync(x => x.UserId.Equals(user.Id) && x.GroupId.Equals(group.Id), cancellationToken);
            if (userGroup == null)
            {
                return IdentityResult.Failed(ErrorDescriber.UserNotInGroup(user.Name, groupName));
            }

            try
            {
                UserGroups.Remove(userGroup);
                await SaveChanges(cancellationToken);
            }
            catch (DbUpdateConcurrencyException)
            {
                return IdentityResult.Failed(ErrorDescriber.ConcurrencyFailure());
            }

            return IdentityResult.Success;
        }
        /// <summary>
        /// 获取组织中所有用户
        /// </summary>
        /// <param name="groupName"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IList<TUser>> GetGroupUsersAsync(string groupName, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            if (groupName.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException(nameof(groupName));
            }

            var query = from _userGroup in UserGroups
                        join _group in Groups on _userGroup.GroupId equals _group.Id
                        join _user in Users on _userGroup.UserId equals _user.Id
                        where _group.Name == groupName
                        select _user;
            return await query.ToListAsync(cancellationToken);
        }
        /// <summary>
        /// 获取用户加入的所有组织名称
        /// </summary>
        /// <param name="user"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IList<string>> GetUserGroupsAsync(TUser user, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            if (user == null || user.Id.Equals(default))
            {
                throw new ArgumentNullException(nameof(user));
            }

            var query = from _userGroup in UserGroups
                        join _group in Groups on _userGroup.GroupId equals _group.Id
                        where _userGroup.UserId.Equals(user.Id)
                        select _group.Name;
            return await query.ToListAsync(cancellationToken);
        }
        /// <summary>
        /// 用户是否在组织中
        /// </summary>
        /// <param name="user"></param>
        /// <param name="groupName"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<bool> IsInGroupAsync(TUser user, string groupName, CancellationToken cancellationToken = default)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();

            if (user == null || user.Id.Equals(default))
            {
                throw new ArgumentNullException(nameof(user));
            }

            if (groupName.IsNullOrWhiteSpace())
            {
                throw new ArgumentNullException(nameof(groupName));
            }

            var query = from _userGroup in UserGroups
                        join _group in Groups on _userGroup.GroupId equals _group.Id
                        where _userGroup.UserId.Equals(user.Id) && _group.Name == groupName
                        select _group.Id;
            return await query.AnyAsync(cancellationToken);
        }
        #endregion

        /// <summary>
        /// 设置用户级别
        /// </summary>
        /// <param name="user"></param>
        /// <param name="level"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task SetLevel(TUser user, string level, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            ThrowIfDisposed();
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            var existLevel = await Levels.FirstOrDefaultAsync(x => x.Name == level, cancellationToken);
            if (existLevel == null)
            {
                throw new ArgumentNullException(nameof(level));
            }

            user.LevelId = existLevel.Id;
        }
    }
}
